#pragma once

#include <functional>

#include "ezstl_allocator.hpp"
#include "ezstl_hash_fun.hpp"
#include "ezstl_hashtable.hpp"

namespace ezstl {

template <typename T>
struct __set_getkey {
  const T &operator()(const T &v) const { return v; }
};

template <typename Value, typename HashFunc = ezstl::hash<Value>,
          typename EqualKey = std::equal_to<Value>,
          typename Alloc = typename Hashtable<
              Value, Value, HashFunc, __set_getkey<Value>, EqualKey>::allocator_type>
class Hash_set {
 private:
  using ht =
      Hashtable<Value, Value, HashFunc, __set_getkey<Value>, EqualKey, Alloc>;
  ht rep;

 public:
  using key_type = typename ht::key_type;
  using value_type = typename ht::value_type;
  using hasher = typename ht::hasher;
  using key_equal = typename ht::key_equal;

  using size_type = typename ht::size_type;
  using pointer = typename ht::pointer;
  using const_pointer = typename ht::const_pointer;
  using reference = typename ht::reference;
  using const_reference = typename ht::const_reference;

  using iterator = typename ht::iterator;
  using const_iterator = typename ht::const_iterator;

  hasher hash_funct() { return rep.hash; }
  key_equal key_eq() { return rep.equals; }

 public:
  Hash_set() : rep{100} {}
  Hash_set(size_type n) : rep{n} {}
  Hash_set(size_type n, const hasher &hf) : rep(n, hf, key_eq()) {}
  Hash_set(size_type n, const hasher &hf, const key_equal &eql)
      : rep{n, hf, eql} {}

 public:
  size_type size() const { return rep.size(); }
  size_type max_size() const { return rep.max_size(); }
  bool empty() const { return rep.empty(); }
  const_iterator begin() const { return rep.begin(); }
  const_iterator end() const { return rep.end(); }
  void clear() { rep.clear(); }
  size_type erase(const key_type &key) { return rep.erase(key); }
  void erase(iterator it) { return rep.erase(it); }
  void resize(size_type hint) { rep.resize(hint); }
  size_type bucket_count() const { return rep.bucket_count(); }
  size_type elems_in_bucket() const {return rep.elems_in_bucket();}

 public:
  std::pair<iterator, bool> insert(const value_type &obj) {
    std::pair<typename ht::iterator, bool> p = rep.insert_unique(obj);
    return std::pair<iterator, bool>{p.first, p.second};
  }
  const_iterator find(const key_type &key) const { return rep.find(key); }
  size_type count(const key_type &key) const { return rep.count(key); }
};

}  // namespace ezstl